package com.ljc.db.backend.dm;

import com.ljc.db.backend.common.AbstractCache;
import com.ljc.db.backend.dm.dataItem.DataItem;
import com.ljc.db.backend.dm.dataItem.DataItemImpl;
import com.ljc.db.backend.dm.logger.Logger;
import com.ljc.db.backend.dm.page.Page;
import com.ljc.db.backend.dm.page.PageOne;
import com.ljc.db.backend.dm.page.PageX;
import com.ljc.db.backend.dm.pageCache.PageCache;
import com.ljc.db.backend.dm.pageIndex.PageIndex;
import com.ljc.db.backend.dm.pageIndex.PageInfo;
import com.ljc.db.backend.tm.TransactionManager;
import com.ljc.db.backend.utils.Panic;
import com.ljc.db.backend.utils.Types;
import com.ljc.db.common.Error;

public class DataManagerImpl extends AbstractCache<DataItem> implements DataManager {

    TransactionManager tm;  // 用于处理事务信息
    PageCache pc;           // 用于获取页面缓存信息
    Logger logger;          // 用于处理日志信息
    PageIndex pIndex;       // 同于处理页的空闲分区信息
    Page pageOne;

    public DataManagerImpl(PageCache pc, Logger logger, TransactionManager tm) {
        super(0);
        this.pc = pc;
        this.logger = logger;
        this.tm = tm;
        this.pIndex = new PageIndex();
    }

    /**
     * 向上层提供读DataItem的服务
     */
    @Override
    public DataItem read(long uid) throws Exception {
        // 通过uid从缓存中读取DataItem
        DataItemImpl di = (DataItemImpl)super.get(uid);
        // 判断是否被逻辑删除
        if(!di.isValid()) {
            // 被逻辑删除就将释放该DataItem的缓存并返回null
            di.release();
            return null;
        }
        // DataItem没有被逻辑删除就可以返回
        return di;
    }

    /**
     * 向上层提供插入数据的服务
     */
    @Override
    public long insert(long xid, byte[] data) throws Exception {
        byte[] raw = DataItem.wrapDataItemRaw(data);
        // 判断要插入的数据是否大于每页的最大容量【插入数据大于1页的容量8KB，无法插入，抛出异常】
        if(raw.length > PageX.MAX_FREE_SPACE) {
            throw Error.DataTooLargeException;
        }
        // 尝试获取一个足以存储插入内容的页面的页号
        PageInfo pi = null;
        for(int i = 0; i < 5; i ++) {
            pi = pIndex.select(raw.length);
            if (pi != null) {
                // 找到了这样的页退出循环
                break;
            } else {
                // 找不到剩余空间足够存储内容的页就新建一个页，并将其加入pageIndex
                int newPgno = pc.newPage(PageX.initRaw());
                pIndex.add(newPgno, PageX.MAX_FREE_SPACE);
                // 再次进入循环查找 -->
            }
        }
        // 如果尝试 5 次还没有新建页成功，说明数据库繁忙，抛出异常
        if(pi == null) {
            throw Error.DatabaseBusyException;
        }

        Page pg = null;
        int freeSpace = 0;
        try {
            // 拿到新建页的页号
            pg = pc.getPage(pi.pgno);
            // 首先做日志
            byte[] log = Recover.insertLog(xid, pg, raw);
            logger.log(log);
            // 再执行插入操作
            short offset = PageX.insert(pg, raw);
            // 将缓存页释放【同时刷到磁盘上】
            pg.release();
            // 返回页内空闲地址偏移量
            return Types.addressToUid(pi.pgno, offset);

        } finally {
            // 将取出的pg重新插入pIndex【方便以后使用】
            if(pg != null) {
                pIndex.add(pi.pgno, PageX.getFreeSpace(pg));
            } else {
                pIndex.add(pi.pgno, freeSpace);
            }
        }
    }

    /**
     * DataManager 正常关闭时，需要执行缓存和日志的关闭流程，不要忘了设置第一页的字节校验
     */
    @Override
    public void close() {
        super.close();
        logger.close();
        // 设置第一页的关闭校验字节【用于下次开启时校验】
        PageOne.setVcClose(pageOne);
        pageOne.release();
        pc.close();
    }

    /**
     * 为xid生成update日志
     */
    public void logDataItem(long xid, DataItem di) {
        byte[] log = Recover.updateLog(xid, di);
        logger.log(log);
    }

    /**
     *  释放该DataItem的缓存
     */
    public void releaseDataItem(DataItem di) {
        super.release(di.getUid());
    }

    /**
     * 根据uid从页缓存中获取 DataItem
     */
    @Override
    protected DataItem getForCache(long uid) throws Exception {
        // 从uid中解析出 页内偏移量
        short offset = (short)(uid & ((1L << 16) - 1));
        uid >>>= 32;
        // 从uid中解析出 页号
        int pgno = (int)(uid & ((1L << 32) - 1));
        // 从缓存中获取页号
        Page pg = pc.getPage(pgno);
        return DataItem.parseDataItem(pg, offset, this);
    }

    /**
     * DataItem 缓存释放，需要将 DataItem 写回数据源，
     * 由于对文件的读写是以页为单位进行的，只需要将 DataItem 所在的页 release 即可【将DataItem所在的整个页写回数据源】
     */
    @Override
    protected void releaseForCache(DataItem di) {
        di.page().release();
    }

    /**
     * 在创建文件时初始化PageOne
     */
    void initPageOne() {
        int pgno = pc.newPage(PageOne.InitRaw());
        assert pgno == 1;
        try {
            pageOne = pc.getPage(pgno);
        } catch (Exception e) {
            Panic.panic(e);
        }
        pc.flushPage(pageOne);
    }

    /**
     * 在打开已有文件时时读入PageOne，并验证正确性
     */
    boolean loadCheckPageOne() {
        try {
            pageOne = pc.getPage(1);
        } catch (Exception e) {
            Panic.panic(e);
        }
        return PageOne.checkVc(pageOne);
    }

    /**
     * DM创建时通过.db文件获取总页数，并填充每页的空闲区间数
     */
    void fillPageIndex() {
        // 读取.db文件，查看总共有多少页
        int pageNumber = pc.getPageNumber();
        // 页面从第二页开始，第一页用来存放校验信息
        for(int i = 2; i <= pageNumber; i ++) {
            Page pg = null;
            try {
                // 循环从页面缓存中获取页
                pg = pc.getPage(i);
            } catch (Exception e) {
                Panic.panic(e);
            }
            // 填充pageIndex中页的空闲分区数量
            pIndex.add(pg.getPageNumber(), PageX.getFreeSpace(pg));
            // 释放掉当前页的缓存【否则可能会撑爆缓存】
            pg.release();
        }
    }
    
}
